﻿using System;
using System.Collections;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using PengSW.ReflectionHelper;

namespace PengSW.XmlHelper
{
    public static class XmlLinqHelper
    {
        public static string GetChildNodeValue(this XElement aXElement, string aChildNodeName, string aDefaultValue)
        {
            if (aXElement == null) return aDefaultValue;
            XElement aChildXElement = aXElement.Element(aChildNodeName);
            if (aChildXElement == null) return aDefaultValue;
            return aChildXElement.Value;
        }

        public static int GetChildNodeValue(this XElement aXElement, string aChildNodeName, int aDefaultValue)
        {
            if (aXElement == null) return aDefaultValue;
            XElement aChildXElement = aXElement.Element(aChildNodeName);
            if (aChildXElement == null) return aDefaultValue;
            return int.TryParse(aChildXElement.Value, out int aValue) ? aValue : aDefaultValue;
        }

        public static DateTime GetChildNodeValue(this XElement aXElement, string aChildNodeName, DateTime aDefaultValue)
        {
            if (aXElement == null) return aDefaultValue;
            XElement aChildXElement = aXElement.Element(aChildNodeName);
            if (aChildXElement == null) return aDefaultValue;
            return DateTime.TryParse(aChildXElement.Value, out DateTime aValue) ? aValue : aDefaultValue;
        }

        public static TimeSpan GetChildNodeValue(this XElement aXElement, string aChildNodeName, TimeSpan aDefaultValue)
        {
            if (aXElement == null) return aDefaultValue;
            XElement aChildXElement = aXElement.Element(aChildNodeName);
            if (aChildXElement == null) return aDefaultValue;
            return TimeSpan.TryParse(aChildXElement.Value, out TimeSpan aValue) ? aValue : aDefaultValue;
        }

        public static double GetChildNodeValue(this XElement aXElement, string aChildNodeName, double aDefaultValue)
        {
            if (aXElement == null) return aDefaultValue;
            XElement aChildXElement = aXElement.Element(aChildNodeName);
            if (aChildXElement == null) return aDefaultValue;
            return double.TryParse(aChildXElement.Value, out double aValue) ? aValue : aDefaultValue;
        }

        public static bool GetChildNodeValue(this XElement aXElement, string aChildNodeName, bool aDefaultValue)
        {
            if (aXElement == null) return aDefaultValue;
            XElement aChildXElement = aXElement.Element(aChildNodeName);
            if (aChildXElement == null) return aDefaultValue;
            return bool.TryParse(aChildXElement.Value, out bool aValue) ? aValue : aDefaultValue;
        }

        public static string GetAttributeValue(this XElement aXElement, string aAttributeName)
        {
            if (aXElement == null) throw new System.ApplicationException($"无法从无效的Xml结点中提取[{aAttributeName}]属性！");
            XAttribute aXAttribute = aXElement.Attribute(aAttributeName);
            if (aXAttribute == null) throw new System.ApplicationException($"在[{aXElement}]中没有找到[{aAttributeName}]属性！");
            return aXAttribute.Value;
        }

        public static string GetAttributeValue(this XElement aXElement, string aAttributeName, string aDefaultValue)
        {
            return aXElement?.Attribute(aAttributeName)?.Value ?? aDefaultValue;
        }

        public static DateTime GetAttributeValue(this XElement aXElement, string aAttributeName, DateTime aDefaultValue)
        {
            if (aXElement == null) return aDefaultValue;
            XAttribute aXAttribute = aXElement.Attribute(aAttributeName);
            if (aXAttribute == null) return aDefaultValue;
            if (!DateTime.TryParse(aXAttribute.Value, out DateTime aValue)) return aDefaultValue;
            return aValue;
        }

        public static TimeSpan GetAttributeValue(this XElement aXElement, string aAttributeName, TimeSpan aDefaultValue)
        {
            if (aXElement == null) return aDefaultValue;
            XAttribute aXAttribute = aXElement.Attribute(aAttributeName);
            if (aXAttribute == null) return aDefaultValue;
            if (!TimeSpan.TryParse(aXAttribute.Value, out TimeSpan aValue)) return aDefaultValue;
            return aValue;
        }

        public static double GetAttributeValue(this XElement aXElement, string aAttributeName, double aDefaultValue)
        {
            if (aXElement == null) return aDefaultValue;
            XAttribute aXAttribute = aXElement.Attribute(aAttributeName);
            if (aXAttribute == null) return aDefaultValue;
            if (!double.TryParse(aXAttribute.Value, out double aValue)) return aDefaultValue;
            return aValue;
        }
        public static int GetAttributeValue(this XElement aXElement, string aAttributeName, int aDefaultValue)
        {
            if (aXElement == null) return aDefaultValue;
            XAttribute aXAttribute = aXElement.Attribute(aAttributeName);
            if (aXAttribute == null) return aDefaultValue;
            if (!int.TryParse(aXAttribute.Value, out int aValue)) return aDefaultValue;
            return aValue;
        }

        public static bool GetAttributeValue(this XElement aXElement, string aAttributeName, bool aDefaultValue)
        {
            if (aXElement == null) return aDefaultValue;
            XAttribute aXAttribute = aXElement.Attribute(aAttributeName);
            if (aXAttribute == null) return aDefaultValue;
            if (!bool.TryParse(aXAttribute.Value, out bool aValue)) return aDefaultValue;
            return aValue;
        }

        public static XElement GetXElement(this XNode aParentXElement, string aXPath, string aPromptName = "")
        {
            XElement aXElement = aParentXElement.XPathSelectElement(aXPath);
            if (aXElement == null) throw new System.ApplicationException($"未找到{aPromptName}：[{aXPath}]");
            return aXElement;
        }

        public static XElement GetXElement(this XNode aParentXElement, string aXPath, IXmlNamespaceResolver aXmlNamespaceResolver, string aPromptName = "")
        {
            XElement aXElement = aParentXElement.XPathSelectElement(aXPath, aXmlNamespaceResolver);
            if (aXElement == null) throw new System.ApplicationException(string.Format("未找到{0}：[{1}]", aPromptName, aXPath));
            return aXElement;
        }

        public static DateTime GetDateTime(this XElement aParentXElement, string aXPath, string aPromptName = "")
        {
            XElement aDateTimeXElement = aParentXElement.GetXElement(aXPath, aPromptName);
            if (!DateTime.TryParse(aDateTimeXElement.Value, out DateTime aDateTime)) throw new ApplicationException(string.Format("无效的日期或时间：[{0}]！", aDateTimeXElement.Value));
            return aDateTime;
        }

        public static TimeSpan GetTimeSpan(this XElement aParentXElement, string aXPath, string aPromptName = "")
        {
            XElement aTimeSpanXElement = aParentXElement.GetXElement(aXPath, aPromptName);
            string aTimeSpanText = System.Text.RegularExpressions.Regex.Replace(aTimeSpanXElement.Value, @"(\d+:\d+:\d+):(\d+)", "$1.$2");
            if (!TimeSpan.TryParse(aTimeSpanText, out TimeSpan aTimeSpan)) throw new ApplicationException(string.Format("无效的日期或时间：[{0}]！", aTimeSpanText));
            return aTimeSpan;
        }

        public static int GetInt(this XElement aParentXElement, string aXPath, string aPromptName = "")
        {
            XElement aIntXElement = aParentXElement.GetXElement(aXPath, aPromptName);
            if (!int.TryParse(aIntXElement.Value, out int aInt)) throw new System.ApplicationException(string.Format("无效的整数值：[{0}]！", aIntXElement.Value));
            return aInt;
        }

        public static int GetInt(this XElement aParentXElement, string aXPath, int aDefaultValue)
        {
            int aInt = aDefaultValue;
            try
            {
                XElement aIntXElement = aParentXElement.GetXElement(aXPath, "");
                int.TryParse(aIntXElement.Value, out aInt);
            }
            catch
            {
            }
            return aInt;
        }

        public static double GetDouble(this XElement aParentXElement, string aXPath, string aPromptName = "")
        {
            XElement aValueXElement = aParentXElement.GetXElement(aXPath, aPromptName);
            if (!double.TryParse(aValueXElement.Value, out double aValue)) throw new System.ApplicationException(string.Format("无效的数值：[{0}]！", aValueXElement.Value));
            return aValue;
        }

        public static bool GetBool(this XElement aParentXElement, string aXPath, string aPromptName = "")
        {
            XElement aValueXElement = aParentXElement.GetXElement(aXPath, aPromptName);
            if (!bool.TryParse(aValueXElement.Value, out bool aValue)) throw new System.ApplicationException(string.Format("无效的逻辑值：[{0}]！", aValueXElement.Value));
            return aValue;
        }

        public static XElement WriteTypeToXml<T>(this T aObject, XElement aXElement, bool aAssemblyEnabled = true)
        {
            if (aObject == null || aXElement == null) return aXElement;
            aXElement.Add(
                aAssemblyEnabled ? new XAttribute("Assembly", aObject.GetType().Assembly.FullName) : null,
                new XAttribute("TypeName", aObject.GetType().FullName));
            return aXElement;
        }

        public static XElement WriteTypeToXml<T>(this XElement aXElement, T aObject, bool aAssemblyEnabled = true)
        {
            return aObject.WriteTypeToXml(aXElement, aAssemblyEnabled);
        }

        public static T CreateObjectFromXml<T>(this XElement aXElement, string aDefaultAssembly = null)
        {
            if (aXElement == null) return default;
            string aAssembly = aXElement.GetAttributeValue("Assembly", null);
            if (aAssembly == null) aAssembly = aDefaultAssembly;
            string aTypeName = aXElement.GetAttributeValue("TypeName", null);
            if (aTypeName == null) return default;
            System.Runtime.Remoting.ObjectHandle aObjectHandler = Activator.CreateInstance(aAssembly, aTypeName);
            T aObject = (T)aObjectHandler.Unwrap();
            return aObject;
        }

        public static int GetInt(this XElement aXElement, int aDefaultValue)
        {
            return int.TryParse(aXElement.Value, out int aValue) ? aValue : aDefaultValue;
        }

        public static bool GetBool(this XElement aXElement, bool aDefaultValue)
        {
            return bool.TryParse(aXElement.Value, out bool aValue) ? aValue : aDefaultValue;
        }

        public static object GetEnum(this XElement aXElement, Type aEnumType, object aDefaultValue)
        {
            try
            {
                return Enum.Parse(aEnumType, aXElement.Value);
            }
            catch
            {
                return aDefaultValue;
            }
        }

        public static byte[] GetBytes(this XElement aXElement)
        {
            if (aXElement == null) return null;
            return System.Convert.FromBase64String(aXElement.Value);
        }

        public static int GetIntAttribute(this XElement aXElement, string aAttributeName, int aDefaultValue)
        {
            try
            {
                return int.Parse(aXElement?.Attribute(aAttributeName)?.Value);
            }
            catch
            {
                return aDefaultValue;
            }
        }

        public static bool GetBoolAttribute(this XElement aXElement, string aAttributeName)
        {
            string aValueString = aXElement.GetAttributeValue(aAttributeName);
            if (!bool.TryParse(aValueString, out bool aValue)) throw new System.ApplicationException(string.Format("[{0}]中的属性[{1}]无法解析为逻辑值！", aXElement, aAttributeName));
            return aValue;
        }

        public static bool GetBoolAttribute(this XElement aXElement, string aAttributeName, bool aDefaultValue)
        {
            try
            {
                return bool.Parse(aXElement.Attribute(aAttributeName).Value);
            }
            catch
            {
                return aDefaultValue;
            }
        }

        public static double GetDoubleAttribute(this XElement aXElement, string aAttributeName)
        {
            string aValueString = aXElement.GetAttributeValue(aAttributeName);
            if (!double.TryParse(aValueString, out double aValue)) throw new System.ApplicationException(string.Format("[{0}]中的属性[{1}]无法解析为数值！", aXElement, aAttributeName));
            return aValue;
        }

        public static double GetDoubleAttribute(this XElement aXElement, string aAttributeName, Double aDefaultValue)
        {
            try
            {
                return double.Parse(aXElement.Attribute(aAttributeName).Value);
            }
            catch
            {
                return aDefaultValue;
            }
        }

        public static double? GetNullableDoubleAttribute(this XElement aXElement, string aAttributeName, double? aDefaultValue)
        {
            if (aXElement == null) return aDefaultValue;
            XAttribute aXAttribute = aXElement.Attribute(aAttributeName);
            if (aXAttribute == null) return aDefaultValue;
            if (!double.TryParse(aXAttribute.Value, out double aValue)) return aDefaultValue;
            return aValue;
        }

        public static Int32? GetNullableIntAttribute(this XElement aXElement, string aAttributeName, Int32? aDefaultValue)
        {
            if (aXElement == null) return aDefaultValue;
            XAttribute aXAttribute = aXElement.Attribute(aAttributeName);
            if (aXAttribute == null) return aDefaultValue;
            if (!Int32.TryParse(aXAttribute.Value, out Int32 aValue)) return aDefaultValue;
            return aValue;
        }

        public static object GetEnumAttribute(this XElement aXElement, string aAttributeName, Type aEnumType, object aDefaultValue)
        {
            try
            {
                return Enum.Parse(aEnumType, aXElement.Attribute(aAttributeName).Value);
            }
            catch
            {
                return aDefaultValue;
            }
        }

        public static XAttribute CreateXAttribute(this object _, string aAttributeName, object aAttributeValue)
        {
            return CreateXAttribute(aAttributeName, aAttributeValue);
        }
        public static XAttribute CreateXAttribute(string aAttributeName, object aAttributeValue)
        {
            if (aAttributeValue == null) return null;
            return new XAttribute(aAttributeName, aAttributeValue);
        }
        public static XAttribute CreateXAttribute(this object _, string aAttributeName, TimeSpan aAttributeValue)
        {
            return CreateXAttribute(aAttributeName, aAttributeValue);
        }
        public static XAttribute CreateXAttribute(string aAttributeName, TimeSpan aAttributeValue)
        {
            if (aAttributeValue == null) return null;
            return new XAttribute(aAttributeName, aAttributeValue.ToString("hh\\:mm\\:ss\\.fff"));
        }
        public static XElement CreateXElement(this byte[] aBytes, string aXmlNodeName)
        {
            if (aBytes == null) return null;
            return new XElement(aXmlNodeName, Convert.ToBase64String(aBytes));
        }

        #region 对象属性的Xml序列化(适用于只含可用Convert转换的一般类型属性的类)

        private static void SetPropertyFromXmlAttribute(object aObject, XElement aXElement, PropertyInfo aPropertyInfo)
        {
            aObject.SetPropertyValue(aPropertyInfo, aXElement.GetAttributeValue(aPropertyInfo.Name, null));
        }

        public static XElement WritePropertiesToXElement(this object aObject, XElement aXElement)
        {
            aXElement.Add(from r in aObject.GetType().GetProperties() select CreateXAttribute(r.Name, r.GetValue(aObject, null)));
            return aXElement;
        }

        public static XElement CreatePropertiesXElement(this object aObject, string aXmlNodeName)
        {
            return aObject.WritePropertiesToXElement(new XElement(aXmlNodeName));
        }

        public static void ReadPropertiesFromXml(this object aObject, XElement aXElement)
        {
            if (aObject == null || aXElement == null) return;
            foreach (var aPropertyInfo in aObject.GetType().GetProperties())
            {
                SetPropertyFromXmlAttribute(aObject, aXElement, aPropertyInfo);
            }
        }

        #endregion

        #region 基于可序列化属性标记XmlSerializableAttribute的Xml序列化

        [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
        public sealed class XmlSeriableAttribute : Attribute
        {
            public XmlSeriableAttribute(string Category = "Properties") { this.Category = Category; }
            public string Category { get; }
        }

        [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
        public sealed class XmlNoseriableAttribute : Attribute
        {
        }

        public static XElement WriteSeriablePropertiesToXElement(this object aObject, XElement aXElement)
        {
            aXElement.Add(
                from r
                in aObject.GetType().GetProperties()
                let aAttribute = r.GetCustomAttribute(typeof(XmlSeriableAttribute)) as XmlSeriableAttribute
                where aAttribute != null
                group r 
                by aAttribute.Category 
                into g
                select new XElement(g.Key, 
                    from p 
                    in g 
                    select CreateXAttribute(p.Name, p.GetValue(aObject, null))));
            return aXElement;
        }

        public static XElement CreateSeriablePropertiesXElement(this object aObject, string aXmlNodeName)
        {
            return aObject.WriteSeriablePropertiesToXElement(new XElement(aXmlNodeName));
        }

        public static void ReadSeriablePropertiesFromXml(this object aObject, XElement aXElement)
        {
            if (aObject == null || aXElement == null) return;
            foreach (var aProperties in 
                from r 
                in aObject.GetType().GetProperties() 
                let aAttribute = r.GetCustomAttribute(typeof(XmlSeriableAttribute)) as XmlSeriableAttribute
                where aAttribute != null
                group r by aAttribute.Category into g select g)
            {
                XElement aCategoryXElement = aXElement.Element(aProperties.Key);
                if (aCategoryXElement == null) continue;
                foreach (var aProperty in aProperties)
                {
                    SetPropertyFromXmlAttribute(aObject, aCategoryXElement, aProperty);
                }
            }
        }

        /// <summary>
        /// 序列化指定对象中带有XmlSerializableAttribute的属性和支持IXmlSeriable接口的属性
        /// </summary>
        public static XElement CreateObjectXElement(this object aObject, string aXmlNodeName)
        {
            // 输出带有XmlSerializableAttribute的属性
            XElement aXElement = aObject.CreateSeriablePropertiesXElement(aXmlNodeName);

            // 输出支持IXmlSeriable的属性
            foreach (var aPropertyInfo in aObject.GetType().GetPropertiesWithInterface(typeof(IXmlSeriable)))
            {
                if (aPropertyInfo.HasAttribute(typeof(XmlNoseriableAttribute))) continue;
                if (aPropertyInfo.GetValue(aObject) is IXmlSeriable aXmlSeriable)
                {
                    aXElement.Add(aXmlSeriable.CreateXElement(aPropertyInfo.Name));
                }
            }

            // TODO: 对IEnumerable的支持
            return aXElement;
        }

        public static void ReadObjectFromXElement(this object aObject, XElement aXElement)
        {
            if (aXElement == null) return;

            // 读入带有XmlSerializableAttribute的属性
            aObject.ReadSeriablePropertiesFromXml(aXElement);

            // 读入支持IXmlSeriable的属性
            foreach (var aPropertyInfo in aObject.GetType().GetPropertiesWithInterface(typeof(IXmlSeriable)))
            {
                if (aPropertyInfo.HasAttribute(typeof(XmlNoseriableAttribute))) continue;
                if (aPropertyInfo.GetValue(aObject) is IXmlSeriable aXmlSeriable)
                {
                    XElement aPropertyXElement = aXElement.Element(aPropertyInfo.Name);
                    if (aPropertyXElement == null) continue;
                    aXmlSeriable.ReadFromXml(aPropertyXElement);
                }
            }

            // TODO: 对IEnumerable的支持
        }

        #endregion

        #region IXmlSeriable一维数组和二维数组的序列化
        
        public static XElement CreateArrayXElement<T>(this T[] aArray, string aXmlNodeName) where T : IXmlSeriable
        {
            if (aArray == null) return null;
            XElement aXElement = new XElement(aXmlNodeName, CreateXAttribute("Count", aArray.Length), from r in aArray select r.CreateXElement("Item"));
            return aXElement;
        }

        public static T[] ReadArrayFromXml<T>(this XElement aXElement) where T : IXmlSeriable, new()
        {
            if (aXElement == null) return null;
            int aCount = aXElement.GetAttributeValue("Count", 0);
            T[] aArray = new T[aCount];
            int i = 0;
            foreach (var aItemXElement in aXElement.Elements("Item"))
            {
                if (i >= aCount) break;
                T aItem = new T();
                aItem.ReadFromXml(aItemXElement);
                aArray[i++] = aItem;
            }
            return aArray;
        }

        #endregion

        public static XElement CreateMatrixXElement<T>(this T[,] aMatrix, string aXmlNodeName) where T : IXmlSeriable
        {
            if (aMatrix == null) return null;
            XElement aXElement = new XElement(aXmlNodeName, CreateXAttribute("Rows", aMatrix.GetLength(0)), CreateXAttribute("Cols", aMatrix.GetLength(1)));
            for (int i = 0; i < aMatrix.GetLength(0); i++)
            {
                for (int j = 0; j < aMatrix.GetLength(1); j++)
                {
                    XElement aItemXElement = aMatrix[i, j].CreateXElement("Item");
                    aItemXElement.Add(CreateXAttribute("Row", i));
                    aItemXElement.Add(CreateXAttribute("Col", j));
                    aXElement.Add(aItemXElement);
                }
            }
            return aXElement;
        }

        public static T[,] ReadMatrixFromXml<T>(this XElement aXElement) where T : IXmlSeriable, new()
        {
            if (aXElement == null) return null;
            int aRows = aXElement.GetAttributeValue("Rows", 0);
            int aCols = aXElement.GetAttributeValue("Cols", 0);
            T[,] aMatrix = new T[aRows, aCols];
            foreach (var aItemXElement in aXElement.Elements("Item"))
            {
                int aRow = aItemXElement.GetAttributeValue("Row", -1);
                int aCol = aItemXElement.GetAttributeValue("Col", -1);
                if (aRow < 0 || aRow >= aRows || aCol < 0 || aCol >= aCols) continue;
                T aItem = new T();
                aItem.ReadFromXml(aItemXElement);
                aMatrix[aRow, aCol] = aItem;
            }
            return aMatrix;
        }
    }
}
